Creating the Conversation Initiator
It’s finally time to create the stored procedure that initiates the dialog between the services. Listing 3 contains the code to do this.
Listing 3. Using BEGIN CONVERSATION DIALOG in a Stored Procedure
CREATE PROCEDURE Production.ProductModelUpdate GO USE AdventureWorks2008 GO DROP PROC Production.ProductModelUpdate GO CREATE PROCEDURE Production.ProductModelUpdate ( @ProductId int, @NewName Name ) AS DECLARE @DialogHandle UNIQUEIDENTIFIER DECLARE @CatalogChangeXml xml (DOCUMENT dbo.CatalogChangeSchema) DECLARE @RemoteSSBGuid uniqueidentifier
-- Get the SSB guid for the target service's db SELECT @RemoteSSBGuid = service_broker_guid FROM sys.databases WHERE name = 'XCatMgmt';
BEGIN TRAN;
UPDATE Production.ProductModel SET Name = pm.Name -- change this to @NewName to actually modify the data FROM Production.ProductModel pm JOIN Production.Product p on p.ProductModelId = pm.ProductModelId WHERE p.ProductId = @ProductId;
if @@ERROR != 0 BEGIN ROLLBACK TRAN RAISERROR('(Initiator) Error during table update', 16, 1) RETURN END;
BEGIN TRY;
WITH XMLNAMESPACES ( 'http://schemas.microsoft.com/sqlserver/2004/07/adventure- works/ProductModelDescription' as p1, 'http://schemas.microsoft.com/sqlserver/2004/07/adventure- works/ProductModelWarrAndMain' as wm, 'http://www.adventure-works.com/schemas/OtherFeatures' as wf, 'http://www.w3.org/1999/xhtml' as html ) SELECT @CatalogChangeXml = CatalogDescription.query(' for $ContextNode in //p1:ProductDescription, $SpecNode in $ContextNode/p1:Specifications, $FeatureNode in $ContextNode/p1:Features return xmlns="urn:www-samspublishing-com:examples:ssb:catalogchange"> ChangeType="2" Price="{sql:column("p.ListPrice")}" ManufacturerId="1" Name="{sql:column("pm.Name")}" SourceProductId="{sql:column("p.ProductId")}"> {$ContextNode/p1:Summary/html:p/text()} Handlebars: {$FeatureNode/wf:handlebar/text()} Wheels: {$FeatureNode/wf:wheel/text()} BikeFrame: {$FeatureNode/wf:BikeFrame/html:i/text()} Material: {$SpecNode/Material/text()} Color: {$SpecNode/Color/text()} ProductLine: {$SpecNode/ProductLine/text()} Style: {$SpecNode/Style/text()} ') FROM Production.ProductModel pm JOIN Production.Product p ON pm.ProductModelId = p.ProductModelId WHERE p.ProductId = @ProductId
END TRY BEGIN CATCH ROLLBACK TRAN RAISERROR('(Initiator) Error during XML production.', 16, 1) RETURN; END CATCH
BEGIN DIALOG CONVERSATION @DialogHandle FROM SERVICE [//samspublishing.com/SS2008/SSB/Services/CatalogChangeInitiatorService] TO SERVICE '//samspublishing.com/SS2008/SSB/Services/CatalogMaintenanceService', @RemoteSSBGuid ON CONTRACT [//samspublishing.com/SS2008/SSB/Contracts/BasicCatalogChangeContract] WITH ENCRYPTION = OFF;
SEND ON CONVERSATION @DialogHandle MESSAGE TYPE [//samspublishing.com/SS2008/SSB/MessageTypes/CatalogChangeMessage] (@CatalogChangeXml) PRINT '(Initiator) Message Sent Successfully.' COMMIT TRAN
|
This UPDATE procedure exemplifies several key concepts. A variable for the dialog handle is declared for later storage during the call to BEGIN DIALOG.
After the actual database update, a typed XML variable—that matches the
same XML schema collection as the message type of which it will become
an instance—is populated, using an XQuery statement.
The call to CatalogDescription.query() transforms the original XML into XML that matches the schema in CatalogChangeSchema. This way, if there are any validation errors, you
find out about them before the message is sent (implicitly terminating
the open transaction). This makes it virtually impossible to send an
invalid message.
The new value for ProductModel.Name is inserted into the XML via the attribute constructor Name="{sql:column("pm.Name")}". The value of the attribute ChangeType="2" corresponds to the enumeration in the schema where id="Update". Because you use this value (as you’ll soon see), the service for XCatMgmt knows what the sender intended by the message.
In the new SEND statement, the saved GUID for the Service Broker instance on XCatMgmt is used to locate the target service when sending the message. SEND has the following syntax:
SEND ON CONVERSATION ConversationHandle
[ MESSAGE TYPE MessageTypeName ]
[ ( MessageBody ) ][ ; ]
As you can see, the SEND statement requires ConversationHandle for message correlation. The type specified in MessageTypeName must match the appropriate type specified in the contract for the sending service. MessageBody must be of a data type that can be converted to varbinary(max), such as xml.
If any issues arise during the sending of a message, you can find the text of the reason for the problems in sys.transmission_queue.transmission_status. This is a great place to look for transmission-related information because the messages in it are reasonably user-friendly.
You also need to consider the use of the END CONVERSATION statement, which, predictably, ends the conversation. This is its syntax:
END CONVERSATION ConversationHandle
[
[ WITH ERROR = ErrorPositiveInt DESCRIPTION = 'ErrorMsg' ]
|
[ WITH CLEANUP ]
][ ; ]
If desired, you can specify an error message value in ErrorPositiveInt
and an error message of your choosing when ending the conversation.
Ending a conversation with an error drops all the unsent messages
currently in the transmission queue, and Service Broker sends a message
to the target service of type Error.
You can specify the WITH CLEANUP
clause to clean up the transmission queue’s unsent messages related to
the conversation and to clear the queue owned by this service (in this
case, Production.CatalogChangeAckQueue).
Note that until both services in the conversation call END CONVERSATION, the conversation is not complete. When only one side calls END CONVERSATION or when the LIFETIME setting of the conversation has been met, the other endpoint can continue to use the invalid conversation handle until the two sides receive the EndDialog message (if messages are sent after the EndDialog message has been received, a runtime error is raised).